﻿using System;
using IOP.Extension.DependencyInjection;
using IOP.SgrA;
using IOP.SgrA.SilkNet.Vulkan;
using Microsoft.Extensions.Logging;
using Silk.NET.Vulkan;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;
using System.Buffers;
using System.Runtime.InteropServices;

namespace VkSample1210
{
    [ModulePriority(ModulePriority.RenderGroup)]
    [ParentModule(typeof(RenderPassModule))]
    public class PipelineModule : VulkanModule, IVulkanRenderWapperModule
    {
        [Autowired]
        private ILogger<PipelineModule> _Logger { get; set; }
        public VulkanRenderWapper RenderWapper { get; set; }

        public SecondaryVulkanRenderGroup CommonGroup { get; set; }

        protected override Task Load(VulkanGraphicsManager graphicsManager)
        {
            try
            {
                if (RenderWapper == null) throw new ArgumentNullException();
                if (Parent is not RenderPassModule passModule) throw new NullReferenceException("Parent is null");
                var lDevice = graphicsManager.VulkanDevice;
                var pipeCache = lDevice.PipelineCache;
                var presentPass = passModule.MainGroup.RenderPass;
                var area = RenderWapper.RenderArea;
                var basePath = AppContext.BaseDirectory;

                var camera = graphicsManager.CreateCamera("main", new Vector3(0, 0f, 6f), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
                float aspcet = (float)area.Width / area.Height;
                camera.SetProjectMatrix(-aspcet, 1.0f, 1.5f, 1000, reversedZ: true);
                camera.SetViewport(0, 0, area.Width, area.Height, 0.0f, 1.0f);
                camera.SetScissor(0, 0, area.Width, area.Height);

                var commonPipeline = graphicsManager.BuildScriptedShaderAndPipeline<CommonPipeline>("Common", presentPass, pipeCache, area);
                var sCommand = lDevice.CreateCommandPool((device, option) =>
                {
                    option.Flags = CommandPoolCreateFlags.ResetCommandBufferBit;
                    option.QueueFamilyIndex = device.WorkQueues[0].FamilyIndex;
                }).CreateSecondaryCommandBuffer();
                var sGroup = graphicsManager.CreateSecondaryVulkanRenderGroup("Common", commonPipeline)
                    .Binding(passModule.MainGroup, sCommand, [], []);
                sGroup.CreateGroupRenderingAction((builder) => builder.Run((group) => Common(group)));
                sGroup.SetCamera(camera);
                CommonGroup = sGroup;
                var scene = RenderWapper.CreateAndCutScene<Scene>("Main", passModule.MainGroup, typeof(TransformSystem));
                scene.Initialization();

            }
            catch (Exception e)
            {
                _Logger?.LogError(e.Message + "\r\n" + e.StackTrace);
            }
            return Task.CompletedTask;
        }

        protected override Task Unload(VulkanGraphicsManager manager)
        {
            return Task.CompletedTask;
        }

        private void Common(SecondaryVulkanRenderGroup group)
        {
            try
            {
                var camera = group.Camera;
                var view = camera.ViewMatrix;
                var project = camera.ProjectionMatrix;
                var cmd = group.SecondaryCommandBuffer;

                CommandBufferInheritanceInfo info = new CommandBufferInheritanceInfo
                {
                    Framebuffer = group.Framebuffer.RawHandle,
                    OcclusionQueryEnable = false,
                    RenderPass = group.RenderPass.RawHandle,
                    Subpass = 0
                };
                cmd.Reset();
                cmd.Begin(CommandBufferUsageFlags.OneTimeSubmitBit |
                    CommandBufferUsageFlags.RenderPassContinueBit, info);
                cmd.BindPipeline(PipelineBindPoint.Graphics, group.Pipeline);

                var viewPort = camera.Viewport;
                var scissor = camera.Scissor;
                cmd.SetViewport(new Viewport(viewPort.X, viewPort.Y, viewPort.Width, viewPort.Height, viewPort.MinDepth, viewPort.MaxDepth));
                cmd.SetScissor(new Rect2D(new Offset2D(scissor.X, scissor.Y), new Extent2D(scissor.Width, scissor.Height)));

                foreach (var context in group.GetContexts())
                {
                    context.SetViewMatrix(in view);
                    context.SetProjectionMatrix(in project);
                    context.SetCamera(camera);
                    var render = context.GetContextRenderObject();
                    var pipeline = context.VulkanPipeline;
                    int size = Marshal.SizeOf<Matrix4x4>();
                    ref MVPMatrix local = ref context.GetMVPMatrix();
                    Matrix4x4 mat = local.GetFinalMatrix();
                    byte[] data = ArrayPool<byte>.Shared.Rent(size);
                    Span<byte> span = data;
                    span = span[..size];
                    mat.ToBytes(ref span, 0);

                    var vro = render.GetComponents<VRO>().First();

                    cmd.BindVertexBuffers(0, [0], vro.VerticesBufferInfo.Buffer);
                    cmd.PushConstants(pipeline.PipelineLayout, ShaderStageFlags.VertexBit, 0, span.ToArray());
                    cmd.Draw(vro.VecticesCount, 1, 0, 0);
                    ArrayPool<byte>.Shared.Return(data);
                }
                cmd.End();
            }
            catch (Exception e)
            {
                _Logger.LogError(e.Message + "\r\n" + e.StackTrace);
                group.Disable();
            }
        }
        private void Sharpening(SecondaryVulkanRenderGroup group)
        {
            try
            {
                var camera = group.Camera;
                var view = camera.ViewMatrix;
                var project = camera.ProjectionMatrix;
                var cmd = group.SecondaryCommandBuffer;

                CommandBufferInheritanceInfo info = new CommandBufferInheritanceInfo
                {
                    Framebuffer = group.Framebuffer.RawHandle,
                    OcclusionQueryEnable = false,
                    RenderPass = group.RenderPass.RawHandle,
                    Subpass = 0
                };
                cmd.Reset();
                cmd.Begin(CommandBufferUsageFlags.OneTimeSubmitBit |
                    CommandBufferUsageFlags.RenderPassContinueBit, info);
                cmd.BindPipeline(PipelineBindPoint.Graphics, group.Pipeline);

                var viewPort = camera.Viewport;
                var scissor = camera.Scissor;
                cmd.SetViewport(new Viewport(viewPort.X, viewPort.Y, viewPort.Width, viewPort.Height, viewPort.MinDepth, viewPort.MaxDepth));
                cmd.SetScissor(new Rect2D(new Offset2D(scissor.X, scissor.Y), new Extent2D(scissor.Width, scissor.Height)));

                foreach (var context in group.GetContexts())
                {
                    context.SetViewMatrix(in view);
                    context.SetProjectionMatrix(in project);
                    context.SetCamera(camera);
                    var render = context.GetContextRenderObject();
                    var pipeline = context.VulkanPipeline;
                    int size = Marshal.SizeOf<Matrix4x4>();
                    ref MVPMatrix local = ref context.GetMVPMatrix();
                    Matrix4x4 mat = local.GetFinalMatrix();
                    byte[] data = ArrayPool<byte>.Shared.Rent(size);
                    Span<byte> span = data;
                    span = span[..size];
                    mat.ToBytes(ref span, 0);

                    var vro = render.GetComponents<VRO>().First();
                    var image = render.GetComponents<VulkanTexture>().First();

                    cmd.BindDescriptorSets(PipelineBindPoint.Graphics, pipeline.PipelineLayout, 0, null, image.DescriptorSet);
                    cmd.BindVertexBuffers(0, [0], vro.VerticesBufferInfo.Buffer);
                    cmd.PushConstants(pipeline.PipelineLayout, ShaderStageFlags.VertexBit, 0, span.ToArray());
                    cmd.Draw(vro.VecticesCount, 1, 0, 0);
                    ArrayPool<byte>.Shared.Return(data);
                }
                cmd.End();
            }
            catch (Exception e)
            {
                _Logger.LogError(e.Message + "\r\n" + e.StackTrace);
                group.Disable();
            }
        }
    }
}
